﻿using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SVF.Tests
{
    /// <summary>
    /// Tests public methods and properties of the SeparatedValueFile class
    /// </summary>
    [TestClass]
    public class SeparatedValuesFileTest
    {
        /// <summary>
        /// Verifies the correct implementation with valid arguments
        /// </summary>
        [TestMethod]
        public void TestConstructorValidArguments()
        {
            new SeparatedValuesFile("\r\n", ",");
            new SeparatedValuesFile("\r\n", "\t");
            new SeparatedValuesFile("\n", ";");
        }

        /// <summary>
        /// Asserts that a null row separator causes an ArgumentNullException
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestConstructorNullRowSeparator()
        {
            new SeparatedValuesFile(null, ",");
        }

        /// <summary>
        /// Asserts that an empty row separator causes an ArgumentNullException
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestConstructorEmptyRowSeparator()
        {
            new SeparatedValuesFile("", ",");
        }

        /// <summary>
        /// Asserts that a null column separator causes an ArgumentNullException
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestConstructorNullColumnSeparator()
        {
            new SeparatedValuesFile("\r\n", null);
        }

        /// <summary>
        /// Asserts that an empty column separator causes an ArgumentNullException
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestConstructorEmptyColumnSeparator()
        {
            new SeparatedValuesFile("\r\n", "");
        }

        /// <summary>
        /// Asserts that the same string for column and row separator cause an ArgumentException
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void TestConstructorSameSeparators()
        {
            new SeparatedValuesFile(",", ",");
        }

        /// <summary>
        /// Asserts that an ArgumentException is thrown if the row separator is sub string of
        /// the column separator
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\row_ss_of_col.sv")]
        [ExpectedException(typeof(ArgumentException))]
        public void TestRowSeparatorSubStringOfColSeparator()
        {
            // If it was possible to parse files where the row separator is a sub string of
            // the column separator, the expected result would be this:
            var expected = new Dictionary<int, string[]>
            {
                {0, new[] {"A", "B", "C"}},
                {1, new[] {"1", "2", "3"}},
                {2, new[] {"X", "Y", "Z"}},
            };

            var svf = new SeparatedValuesFile("|", "||");
            svf.Load("row_ss_of_col.sv");

            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Prove that it is possible to parse file where the column separator is
        /// sub string of the row separator.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\col_ss_of_row.sv")]
        public void TestColSeparatorSubStringOfRowSeparator()
        {
            var expected = new Dictionary<int, string[]>
            {
                {0, new[] {"A", "B", "C"}},
                {1, new[] {"1", "2", "3"}},
                {2, new[] {"X", "Y", "Z"}},
            };

            var svf = new SeparatedValuesFile("||", "|");
            svf.Load("col_ss_of_row.sv");

            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Assert that the method Load() executes without an exception
        /// if a valid file is provided.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_medium.csv")]
        public void TestLoadValidFile()
        {
            var svf = new SeparatedValuesFile("\r\n", ",");
            svf.Load(@".\ansi_medium.csv");
        }

        /// <summary>
        /// Assert that the method Load() throws an ArgumentException
        /// if the file cannot be read
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void TestLoadInvalidFile()
        {
            var csv = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

            // Make sure the file does not exist
            while (File.Exists(csv)) csv = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load(csv);
        }

        /// <summary>
        /// Assert that the Row property returns the expected number of
        /// rows after Load() has been called.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_medium.csv")]
        public void TestRowPropery()
        {
            const int expectedRowCount = 42;

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load(@".\ansi_medium.csv");

            // Assert all the available rows were parsed
            Assert.AreEqual(expectedRowCount, svf.Rows, "Invalid number of rows were parsed");
        }

        /// <summary>
        /// Assert that the method Load() parsed the file data correctly
        /// and that the indexer provides correct access to the data.
        /// The file is an ANSI text file created with Notepad.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_medium.csv")]
        public void TestIndexerAnsiWin()
        {
            const int expectedRowCount = 42;

            var expected = new Dictionary<int, string[]> 
            {
                { 0, new[] { "Stunde", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag" }},
                { 3, new[] {  "3", "Sport", "Religion ev/kath", "Kunst", "Mathe", "Kunst" }},
                {10, new[] { "10", "Sport", "Italienisch", "Geschichte", "Sport" , "Geschichte" }},
                {40, new[] { "40", "Sport", "Italienisch", "Mathe", "Deutsch" , "Englisch" }},
            };

            var svf = new SeparatedValuesFile("\r\n", ",");
            svf.Load(@".\ansi_medium.csv");

            var actual = svf[0];

            Assert.IsNotNull(actual, "No data were returned at all");

            // Assert all the available rows were parsed
            Assert.AreEqual(expectedRowCount, svf.Rows, "Invalid number of rows were parsed");

            // Assert that by example that columns were parsed exactly
            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Assert that the method Load() parsed the file data correctly
        /// and that the indexer provides correct access to the data.
        /// The file is an ANSI text file created on a Linux machine
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_medium_linux.csv")]
        public void TestIndexerAnsiNix()
        {
            const int expectedRowCount = 42;

            var expected = new Dictionary<int, string[]> 
            {
                { 0, new[] { "Stunde", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag" }},
                { 3, new[] {  "3", "Sport", "Religion ev/kath", "Kunst", "Mathe", "Kunst" }},
                {10, new[] { "10", "Sport", "Italienisch", "Geschichte", "Sport" , "Geschichte" }},
                {40, new[] { "40", "Sport", "Italienisch", "Mathe", "Deutsch" , "Englisch" }},
            };

            var svf = new SeparatedValuesFile("\n", ",");
            svf.Load(@".\ansi_medium_linux.csv");

            var actual = svf[0];

            Assert.IsNotNull(actual, "No data were returned at all");

            // Assert all the available rows were parsed
            Assert.AreEqual(expectedRowCount, svf.Rows, "Invalid number of rows were parsed");

            // Assert that by example that columns were parsed exactly
            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }


        /// <summary>
        /// Assert that the method Load() parsed the UTF-8 file data correctly
        /// and that the indexer provides correct access to the data.
        /// The test file has entries with a different number of columns.
        /// This method asserts that all available columns were loaded and
        /// it asserts the correct parsing of columns by verfying samples.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\utf-8_medium.csv")]
        public void TestIndexerUtf8()
        {
            var expected = new Dictionary<int, string[]> 
            {
                { 0, new[] { "Stunde", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag" }},
                { 3, new[] {  "3", "Sport", "Religion ev/kath", "", "Kunst", "Kunst" }},
                {10, new[] { "10", "Sport", "Französisch", "Geschichte", "Sport" , "Geschichte" }},
                {40, new[] { "40", "Sport", "Französisch", "Mathe", "Deutsch" , "Englisch" }},
            };

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load(@".\utf-8_medium.csv");

            var actual = svf[0];

            Assert.IsNotNull(actual, "No data were returned at all");

            // Assert that by example that columns were parsed exactly
            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Assert that the method Load() parsed the Unicode file data correctly
        /// and that the indexer provides correct access to the data.
        /// The test file has entries with a different number of columns.
        /// This method asserts that all available columns were loaded and
        /// it asserts the correct parsing of columns by verfying samples.
        /// </summary>
        /// <remarks>
        /// The expected result is modelled in a dictionary where the row number
        /// maps to the expected columns. Such a model can be easily reused and
        /// adapted to other test cases.
        /// </remarks>
        [TestMethod]
        [DeploymentItem(@"TestFiles\unicode_medium.csv")]
        public void TestIndexerUnicode()
        {
            var expected = new Dictionary<int, string[]> 
            {
                { 0, new[] { "Stunde", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag" }},
                { 3, new[] {  "3", "Sport", "Religion ev/kath", "", "Kunst", "Kunst" }},
                {10, new[] { "10", "Sport", "Französisch", "Geschichte", "Sport" , "Geschichte" }},
                {40, new[] { "40", "Sport", "Französisch", "Mathe", "Deutsch" , "Englisch" }},
            };

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load(@".\unicode_medium.csv");

            var actual = svf[0];

            Assert.IsNotNull(actual, "No data were returned at all");

            // Assert that by example that columns were parsed exactly
            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Assert that the method Load() parsed the Unicode file data correctly
        /// and that the indexer provides correct access to the data.
        /// The test file has entries with a different number of columns.
        /// This method asserts that all available columns were loaded and
        /// it asserts the correct parsing of columns by verfying samples.
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\unicode_be_medium.csv")]
        public void TestIndexerUnicodeBigEndian()
        {
            var expected = new Dictionary<int, string[]> 
            {
                { 0, new[] { "Stunde", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag" }},
                { 3, new[] {  "3", "Sport", "Religion ev/kath", "", "Kunst", "Kunst" }},
                {10, new[] { "10", "Sport", "Französisch", "Geschichte", "Sport" , "Geschichte" }},
                {40, new[] { "40", "Sport", "Französisch", "Mathe", "Deutsch" , "Englisch" }},
            };

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load(@".\unicode_be_medium.csv");

            var actual = svf[0];

            Assert.IsNotNull(actual, "No data were returned at all");

            // Assert that by example that columns were parsed exactly
            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Assert that the method Load() parsed the ANSI file data correctly
        /// and that the indexer provides correct access to the data.
        /// This method asserts that all available columns were loaded and
        /// it asserts the correct parsing of columns by verfying samples.
        /// This test executes on a tab separated values file (TSV).
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_medium.tsv")]
        public void TestIndexerAnsi()
        {
            var expected = new Dictionary<int, string[]>
            {
                {0, new[] {"1", "A", "Anton", "Alpha"}},
                {12, new[] {"13", "M", "Martha", "Mike"}},
                {25, new[] {"26", "Z", "Zeppelin", "Zulu"}},
            };

            var svf = new SeparatedValuesFile("\r\n", "\t");
                svf.Load("ansi_medium.tsv");

            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Tests if the method parses correctly if the file has empty rows and
        /// columns. There must be a difference between a row full of emtpy
        /// columns and a row without columns.
        /// </summary>
        /// <remarks>
        /// The used test file is very small so the whole result can be
        /// easily asserted via code.
        /// </remarks>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_empty_rows_and_cols.csv")]
        public void TestAnsiEmptyRowsAndCols()
        {
            var expected = new Dictionary<int, string[]>
            {
                {00, new[] {"1", "A", "B", "C"}},
                {01, new[] {"2", "D", "E", "F"}},
                {02, new[] {"3", "G", "H", "I"}},
                {03, new string[0]},                // Row without columns
                {04, new[] {"4", "J", "", "L"}},
                {05, new[] {"5", "M", "N", ""}},
                {06, new[] {"",  "P", "Q", "R"}},
                {07, new string[0]},                // Row without columns
                {08, new[] {"A", "B", "C", "D"}},
                {09, new[] {"1", "2", "3", ""}},
                {10, new[] {"E",  "F", "", ""}},
                {11, new[] {"", "", "", ""}},       // Row with emty columns
                {12, new string[0]},                // Row without columns
            };

            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load("ansi_empty_rows_and_cols.csv");

            Assert.AreEqual(expected.Count, svf.Rows, "Invalid number of rows were parsed");

            foreach (var rowNumber in expected.Keys)
            {
                var expectedColumnData = expected[rowNumber];
                var actualColumnData = svf[rowNumber];

                for (var i = 0; i < expectedColumnData.Length; i++) Assert.IsTrue(String.Equals(expectedColumnData[i], actualColumnData[i], StringComparison.Ordinal), "Unexpected data in column {0} of row {1}", i, rowNumber);
            }
        }

        /// <summary>
        /// Asserts than in IndexOutOfRangeException exception is thrown if the index is too small
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_empty_rows_and_cols.csv")]
        [ExpectedException(typeof(IndexOutOfRangeException))]
        public void TestIndexerIndexOutOfRangeTooSmall()
        {
            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load("ansi_empty_rows_and_cols.csv");

            var cols = svf[-1];
        }

        /// <summary>
        /// Asserts than in IndexOutOfRangeException exception is thrown if the index is too large
        /// </summary>
        [TestMethod]
        [DeploymentItem(@"TestFiles\ansi_empty_rows_and_cols.csv")]
        [ExpectedException(typeof(IndexOutOfRangeException))]
        public void TestIndexerIndexOutOfRangeTooLarge()
        {
            var svf = new SeparatedValuesFile("\r\n", ",");
                svf.Load("ansi_empty_rows_and_cols.csv");

            var cols = svf[999999999];
        }
    }
}